/*企鹅在冰块上滑动，碰到冰块边缘反弹，玩家可通过切割冰面来收集冰块。
切割时若切割到企鹅游戏失败，若切下的冰块总和达到一定数量则游戏完成。
游戏过程中可按esc键退出游戏回到菜单，菜单中可开关音效，结束程序，看背景小故事。*/
#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <iostream>
#define High 490 // 游戏画面尺寸
#define Width 600

#pragma comment(lib,"Winmm.lib")

int success;
int back;//返回的判断
int cnt;
int eX, eY;//企鹅坐标
int v_ex, v_ey;//企鹅速度
int knife_x[2], knife_y[2];//定义刀的位点
int board_x[80], board_y[80];
int cross_x[2], cross_y[2];//刀与边界的交点
int posi_board[2];//定义交点两侧的边界顶点的下标
int len_board;//边界顶点的个数
int a = 0;//交点cross的下标
int i;//刀的位点的数组下标

IMAGE img_elf, img_eym;//定义企鹅精灵图和掩码图30*35
IMAGE img_ice;//定义游戏冰块360*350
IMAGE img_background;//定义画布背景

 //游戏里的函数的定义
void startup();				// 游戏————数据初始化
void show();				// 游戏————显示画面
void updateWithoutInput();  // 游戏————与用户输入无关的更新
void button();		        // 游戏————用户按键,改变企鹅运动方向
void slice();				// 游戏————切的操作
void gameover();			// 游戏————游戏结束
int inside(int board_num, int *board_x, int *board_y, int x, int y);//游戏————判断点是否在任意多边形内
void newBoard();	// 游戏————记录新边界

void startup()  // 游戏————数据初始化
{
	loadimage(&img_elf, "E://企鹅elf.jpg");//读取企鹅
	loadimage(&img_eym, "E://企鹅ym.jpg");
	loadimage(&img_ice, "E://Ice.jpg", Width * 3 / 5, High * 5 / 7);//读取冰块

	mciSendString("open E:\\bkmusic.mp3 alias bkmusic", NULL, 0, NULL);//背景音
	mciSendString("play bkmusic repeat", NULL, 0, NULL);//循环播放

	eY = High / 2;	//初始化企鹅位置
	eX = Width / 2;
	v_ex = -1;	//初始化企鹅速度方向
	v_ey = 1;

	i = 0;

	int j;
	for (j = 0; j < 80; j++)
	{
		board_x[j] = 0;
		board_y[j] = 0;
	}
	for (j = 0; j < 2; j++)
	{
		cross_x[j] = 0;
		cross_y[j] = 0;
		posi_board[j] = 0;
		knife_x[j] = 0;
		knife_y[j] = 0;
	}
	//初始化边界顶点坐标，为矩形Width*2/3,High*15/16
	board_x[0] = Width / 5;
	board_y[0] = High / 7;
	board_x[1] = Width * 4 / 5;
	board_y[1] = board_y[0];
	board_x[2] = board_x[1];
	board_y[2] = High * 6 / 7;
	board_x[3] = board_x[0];
	board_y[3] = board_y[2];
	board_x[4] = board_x[0];
	board_y[4] = board_y[0];
	len_board = 5;

	FlushMouseMsgBuffer();
	BeginBatchDraw();
}

//posi为刀进入冰块的点
void newBoard()
{
	int s, r;
	int len_area1, len_area2;//区域1和2所含顶点的个数
							 //中间posi_board[0]-posi_board[1]个边界点，posi_knife[0]-posi_knife[1]个刀痕折点
	int area1_x[80];
	int area1_y[80];
	int area2_x[80];
	int area2_y[80];

	for (s = 0; s < 80; s++)
	{
		area1_x[s] = 0;
		area1_y[s] = 0;
		area2_x[s] = 0;
		area2_y[s] = 0;
	}

	for (s = 0; s < posi_board[0]; s++)//从初始边界点到进入进入边界点
	{
		area1_x[s] = board_x[s];
		area1_y[s] = board_y[s];
	}

	area1_x[s] = cross_x[0];//加入第一个相交点
	area1_y[s] = cross_y[0];
	s++;
	area1_x[s] = cross_x[1];//加入第二个相交点
	area1_y[s] = cross_y[1];
	s++;

	r = posi_board[1];
	for (s = s; r < len_board; s++)//从刀痕出去点到边界终点(含board[0])
	{
		area1_x[s] = board_x[r];
		area1_y[s] = board_y[r];
		r++;
	}
	len_area1 = s;

	//记录第二个区域
	area2_x[0] = cross_x[1];//添入第二个交点
	area2_y[0] = cross_y[1];
	area2_x[1] = cross_x[0];//加入第一个相交点
	area2_y[1] = cross_y[0];

	r = posi_board[0];
	for (s = 2; r <posi_board[1]; s++)//从边界上出去点到进入点
	{
		area2_x[s] = board_x[r];
		area2_y[s] = board_y[r];
		r++;//顺时针存入
	}

	area2_x[s] = cross_x[0];//将第一个存入的点加进来以便构成圈
	area2_y[s] = cross_y[0];
	s++;
	len_area2 = s;

	//判断企鹅在哪个区域里以便消除另一个区域
	if (inside(len_area1, area1_x, area1_y, eX, eY))
	{//如果企鹅在区域1里面
		for (s = 0; s < len_area1; s++)
		{//新边界为区域1
			board_x[s] = area1_x[s];
			board_y[s] = area1_y[s];
		}
		len_board = len_area1;
	}
	else if (inside(len_area2, area2_x, area2_y, eX, eY))
	{//企鹅在区域2里面
		for (s = 0; s < len_area2; s++)
		{//新边界为区域2
			board_x[s] = area2_x[s];
			board_y[s] = area2_y[s];
		}
		len_board = len_area2;
	}
}
//判断是否相交
//(X1,Y1)(X2,Y2)线board
//(X3,Y3)(X4,Y4)线knife1,0
//(cross_x,cross_y)交点坐标
//相交return 1不相交return 0
//还不够精确，k2有时会被记为0
int Cross(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4)
{
	double k1, k2;//k1为board斜率，k2为knife斜率
	if (X1 != X2)//边界不竖直
	{
		k1 = (Y2 - Y1) / (X2 - X1);
		if (X3 != X4)//刀痕不竖直
		{
			k2 = (Y4 - Y3) / (X4 - X3);
			if (k1 != k2)//斜率相同时边界点在刀痕所在直线上，相当于未更新，不在相当于不想交于该边界线段
			{			//斜率不相同时两直线可有交点
						//判断交点是否在两线段之间
				if (((X1 - (Y3 - Y1 + X1*k1 - X3*k2) / (k1 - k2))*(X2 - (Y3 - Y1 + X1*k1 - X3*k2) / (k1 - k2)) <= 0)
					&& ((X3 - (Y3 - Y1 + X1*k1 - X3*k2) / (k1 - k2))*(X4 - (Y3 - Y1 + X1*k1 - X3*k2) / (k1 - k2)) <= 0))
				{
					cross_x[a] = (Y3 - Y1 + X1*k1 - X3*k2) / (k1 - k2);//求交点
					cross_y[a] = k1*(cross_x[a] - X1) + Y1;
					return 1;
				}
			}
		}
		else//刀痕竖直,k2不存在,判断带入X3到board线上时的点是否在刀痕线段和边界线段上
		{
			if ((k1 * (X3 - X1) + Y1 - Y3) * (k1 * (X3 - X1) + Y1 - Y4) <= 0 && (X3 - X1) * (X3 - X2) <= 0)
			{
				cross_x[a] = X3;
				cross_y[a] = k1*(X3 - X1) + Y1;
				return 1;
			}
		}
	}
	else//边界竖直
	{
		if (X3 != X4)//刀痕不竖直，带入X1。均竖直相当于未更新
		{
			k2 = (Y4 - Y3) / (X4 - X3);
			if ((k2 * (X1 - X3) + Y3 - Y1) * (k2 * (X1 - X3) + Y3 - Y2) <= 0 && (X1 - X3)*(X1 - X4) <= 0)
			{	//带入X1到knife:y=k2*(x-x3)+y3中求直线交点，判断交点（X3,k1 * (X3 - X1) + Y1)是否在两线段上
				cross_x[a] = X1;
				cross_y[a] = k2 * (X1 - X3) + Y3;
				return 1;
			}
		}
	}
	return 0;
}
/*
board_num 测试多边形的顶点数
board_x    测试多边形的各个顶点的X轴坐标
board_y    测试多边形的各个顶点的Y轴坐标
x 测试点的X轴坐标
Y 测试点的Y轴坐标
返回值: 返回0 表示不在多边形内部，返回1 表示在多边形内部
说明: 在多边形各边上的点默认不在多边形内部 */

int inside(int board_num, int *board_x, int *board_y, int x, int y)//判断点(x, y)是否在有board_num个顶点的多边形内
{
	int j, k;
	j = board_num - 1;
	int res = 0;
	for (k = 0; k < board_num; k++)
	{//对每一条边进行遍历，该边的两个端点，有一个必须在待检测点(x,y)的左边，且两个点中，有一个点的y比p.y小，另一个点的y比p.y大。
		if ((board_y[k] < y&& board_y[j]>y || board_y[j] < y && board_y[k] >= y)
			&& (board_x[k] <= x || board_x[j] <= x))
		{
			//用水平的直线与该边相交，球交点x的坐标
			res ^= ((board_x[k] + (y - board_y[k]) / (board_y[j] - board_y[k])*(board_x[j] - board_x[k])) < x);
		}
		j = k;
	}
	return res;//如res=1，在内部，res=0，不在内部
}

void show()  // 游戏————显示画面
{
	int j;
	putimage(Width / 5, High / 7, &img_ice);//放冰块

	setlinecolor(LIGHTBLUE);
	setlinestyle(PS_DASH | PS_ENDCAP_ROUND, 2);
	if (knife_x[0] != 0)//左键按下后松开前
		line(knife_x[0], knife_y[0], knife_x[1], knife_y[1]);

	//更新画布												 
	int r,s;
	cnt = 0;
	for (s = 1; s < len_board; s++)
	{
		if (board_x[s] != board_x[s-1])
		{
			double k3;
			k3 = (board_y[s-1] - board_y[s]) / (board_x[s-1] - board_x[s]);
			if (k3 < 0 && eY < k3*(eX - board_x[s-1]) + board_y[s-1])
			{
				for (j = (High*6 / 7 - board_y[s]) / k3 + board_x[s]; j < Width*4/5; j++)
				{
					for (r = k3*(j - board_x[s]) +board_y[s]; r < High * 6 / 7; r++)
					{
						putpixel(j, r, BLACK);
						cnt++;
					}
				}
			}
			else if (k3<0 && eY> k3*(eX - board_x[s]) + board_y[s])
			{
				for (j = Width/5; j < (High/7-board_y[s])/k3+board_x[s]; j++)
				{
					for (r = High / 7; r < k3*(j - board_x[s]) + board_y[s]; r++)
					{
						putpixel(j, r, BLACK);
						cnt++;
					}
				}
			}
			else if (k3 > 0 && eY > k3*(eX - board_x[s]) + board_y[s])
			{
				for (j = (High / 7 - board_y[s]) / k3 + board_x[s]; j < High*6/7; j++)
				{
					for (r = High / 7; r < k3*(j - board_x[s]) + board_y[s]; r++)
					{
						putpixel(j, r,BLACK);
						cnt++;
					}
				}
			}
			else if (k3 > 0 && eY < k3*(eX - knife_x[0]) + knife_y[0])
			{
				for (j = High/7; j < (High * 6 / 7 - board_y[s]) / k3 + board_x[s]; j++)
				{
					for (r = k3*(j - board_x[s]) + board_y[s]; r < High * 6 / 7; r++)
					{
						putpixel(j, r, BLACK);
						cnt++;
					}
				}
			}
			else if (k3 == 0)
			{
				if (eY > board_y[1])
				{
					for (j = Width / 5; j < Width * 4 / 5; j++)
					{
						for (r = High / 7; r < board_y[1]; r++)
						{
							putpixel(j, r, BLACK);
							cnt++;
						}
					}
				}
				else
				{
					for (j = Width / 5; j < Width * 4 / 5; j++)
					{
						for (r = board_y[1]; r < High * 6 / 7; r++)
						{
							putpixel(j, r, BLACK);
							cnt++;
						}
					}
				}
			}
		}
		else
		{
			if (eX > board_x[s])
			{
				for (j = Width / 5; j < board_x[s]; j++)
				{
					for (r = High / 7; r < High * 6 / 7; r++)
					{
						putpixel(j, r, BLACK);
						cnt++;
					}
				}
			}
			else
			{

				for (j = board_x[s]; j < High * 6 / 7; j++)
				{
					for (r = High / 7; r < High * 6 / 7; r++)
					{
						putpixel(j, r, BLACK);
						cnt++;
					}
				}
			}
		}
	}
	for (j = 0; j < len_board; j++)
	{
		circle(board_x[j], board_y[j], 5);
	}
	putimage(eX - 15, eY - 17, &img_eym, NOTSRCERASE);//画企鹅掩码图
	putimage(eX - 15, eY - 17, &img_elf, SRCINVERT);       //精灵图

	setbkmode(TRANSPARENT);
	setbkcolor(LIGHTRED);
	settextstyle(50, 0, _T("黑体"));
	if (cnt > 8100)
	{
		if (cnt > 8100 && cnt < 14400)
		{
			outtextxy(200, 100, "win!class 3");
			gameover();
		}
		else if (cnt > 14400 && cnt < 32400)
		{
			outtextxy(200, 100, "win!class 2");
			gameover();
		}
		else if (cnt > 32400)
		{
			outtextxy(200, 100, "win!class 3");
			gameover();
		}
		//胜利音效
		mciSendString("close win", NULL, 0, NULL);//先把前一次背景乐关掉
		mciSendString("open E:\\win.mp3 alias win", NULL, 0, NULL);//打开跳动音乐
		mciSendString("play win", NULL, 0, NULL);//仅播放一次
	}
	FlushBatchDraw();
}

void updateWithoutInput()  // 游戏————与用户输入无关的更新
{
	// 更新企鹅坐标
	eX += v_ex;
	eY += v_ey;

	double k1, k2;//定义企鹅与边界点的连线的斜率
	int j;
	for (j = 1; j < len_board; j++)
	{
		if (board_x[j] == board_x[j - 1] && abs(eX - board_x[j])<15)//企鹅在竖直边界上
			v_ex = -v_ex;
		else if (board_y[j] == board_y[j - 1] && abs(eY - board_y[j])<17)//在水平边界上
			v_ey = -v_ey;
		else //在倾斜边上
		{
			k1 = (eY - board_y[j]) * 1.0 / (eX - board_x[j]);
			k2 = (eY - board_y[j - 1]) * 1.0 / (eX - board_x[j - 1]);
			if (k1 == k2)//企鹅在边界线上
			{
				/*v_ex = k1*k1*(eX - 2 * eY + v_ey) + k1*eY - v_ey;
				v_ey = 2 * k1*k1*k1*(v_ey - eY) + k1*k1*(2 * eY - v_ey) + 2 * k1*(2 * eX - v_ex) - v_ey;*/
				k1 = -1 / k1;
				v_ex = -v_ex;
				v_ey = -v_ey;
			}
		}
	}
}

void button()  // 游戏————用户按键,更新
{
	if (MouseHit())		//检测是否有鼠标消息
	{
		MOUSEMSG m;		//定义鼠标消息
		m = GetMouseMsg();

		if (abs(m.x - eX) < 20 && abs(m.y - eY) < 20)//点击企鹅
		{
			if (m.uMsg == WM_LBUTTONDOWN)//单击左键，转向
				v_ey = -v_ey;
			if (m.uMsg == WM_RBUTTONDOWN)//单击右键，反向
			{
				v_ex = -v_ex;
				v_ey = -v_ey;
			}
		}
	}
	char input;
	if (kbhit())
	{
		input = _getch();
		if (input == 27)//按esc键
		{
			gameover();
		}
	}
}

void slice()//游戏————切的操作
{
	MOUSEMSG m;			//定义鼠标消息
LOOP:
	cleardevice();	//用当前背景色清空上一次图片

	if (MouseHit())//检测是否有鼠标消息
	{
		m = GetMouseMsg();
		switch (m.uMsg)
		{
		case WM_MOUSEMOVE:	//若鼠标消息为鼠标移动
		{
			if (m.mkLButton)	//若按下左键
			{
				//切冰块音效
				mciSendString("close ice_slice", NULL, 0, NULL);//先把前一次背景乐关掉
				mciSendString("open E:\\ice_slice.mp3 alias ice_slice", NULL, 0, NULL);//打开跳动音乐
				mciSendString("play ice_slice", NULL, 0, NULL);//仅播放一次

				if (i == 0)//画笔初始点
				{
					knife_x[0] = m.x;
					knife_y[0] = m.y; //记录获取的鼠标的坐标
					i = 1;
				}
				if (i == 1)
				{
					knife_x[1] = m.x;
					knife_y[1] = m.y;
				}
				if (pow((m.x - knife_x[0]), 2) + pow((m.y - knife_y[0]), 2)>500000)//超过一定长度
				{
					outtextxy(m.x,m.y,"太长了");
					i = 0;
					int p;
					for (p = 0; p < 2; p++)//清空数组
					{
						knife_x[p] = 0;
						knife_y[p] = 0;
					}
					goto LOOP;
				}
			}
			else if (WM_LBUTTONUP)//鼠标左键松开状态，可能是还未按下左键，也可能是按下左键后松开
			{				//按下左键后松开时knife不为0，执行完这个语句块后再将knife清0，此后是还未按下，就不能执行以下语句
				if (knife_x[1] != 0)	//最后所得的knife[1]即为刀的末尾
				{
					circle(knife_x[1], knife_y[1], 5);
					a = 0;//每切一次，交点下标a就要初始化为0
					int j, r;//下标
					float k1, k2;//斜率

					for (j = 0; board_x[j] != 0; j++);
					len_board = j;//顶点个数

					 //是更新画布的循环拖慢了程序,画笔可以随便画，但board明显未更新，j>5时if语句未执行
				  //判断刀是否落在冰块里面
					if ((!inside(len_board, board_x, board_y, knife_x[0], knife_y[0])) &&
						(!inside(len_board, board_x, board_y, knife_x[1], knife_y[1])))
					{
						k1 = (knife_y[0] - eY) * 1.0 / (knife_x[0] - eX);//求企鹅刀的两端点连线的斜率
						k2 = (knife_y[1] - eY) * 1.0 / (knife_x[1] - eX);

						if (k1 == k2)//如果切到企鹅
						{
							success = 0;//游戏失败
							gameover();//若切到，直接跳入gameover函数,以下语句不再执行
						}
						//未切到企鹅
						int cross = 0;//判断是否相交
						for (r = 1; r < len_board; r++)
						{
							cross = Cross(board_x[r], board_y[r], board_x[r - 1], board_y[r - 1], knife_x[1], knife_y[1], knife_x[0], knife_y[0]);
							//找出交点
							if (cross == 1)
							{
								if (a == 0)
								{
									a = 1;
									posi_board[0] = r;
								}
								else if (a == 1)
								{
									posi_board[1] = r;
									//cross[]和posi_baord[]的0,1与在边界上的顺序有关，靠近board[0]的下标为0
									newBoard();//传入1表示从1进入且与边界顶点存储方向相反
									a = 0;
									break;
								}
							}
						}
					}
					i = 0;
					for (j = 0; j < 2; j++)//清空数组
					{
						knife_x[j] = 0;
						knife_y[j] = 0;
					}
					FlushMouseMsgBuffer();
					//goto LOOP;

				}
			}
		}
		}
	}
}

void gameover()		//游戏————游戏结束 success为0时失败，为1时胜利
{
	EndBatchDraw();
	putimage(Width, High, &img_ice);
	if (success == 0)
	{
		setbkmode(TRANSPARENT);
		setbkcolor(LIGHTBLUE);
		settextstyle(50, 0, _T("黑体"));
		outtextxy(200, 100, "游戏失败");
	}
	_getch();
	closegraph();
}

void backstory()		//主页菜单————跳转背景故事
{
	int hit = 0;	//总共点击的次数
	back = 0;
	IMAGE img_help1, img_help2, img_help3;//定义帮助图册
	cleardevice();//清空画布
	loadimage(&img_help1, "E://help1.jpg", Width * 2 / 3, High);//1图册大小
	putimage(Width / 6, 0, &img_help1);
	while (back == 0)
	{
		if (MouseHit())
		{
			MOUSEMSG m;	//定义鼠标消息
			m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN)//如果单击左键
			{
				hit++;
				setlinecolor(LIGHTBLUE);//鼠标点击显示
				circle(m.x, m.y, 5);

				if (hit == 1)
				{
					loadimage(&img_help2, "E://help2.jpg", Width * 2 / 3, High);//2图册
					putimage(Width / 6, 0, &img_help2);
				}
				else if (hit == 2)
				{
					loadimage(&img_help3, "E://help3.jpg", Width * 2 / 3, High);//3图册
					putimage(Width / 6, 0, &img_help3);
				}
				else if (hit == 3)
				{
					back = 1;	//帮助结束，返回菜单
					cleardevice();
					break;
				}
			}
		}
	}
}

void game()		//主页菜单————开始游戏
{
	startup();  // 数据初始化
	while (1)  //  游戏循环执行
	{
		updateWithoutInput();  // 与用户输入无关的更新
		button();  // 用户按键,更新
		slice();//切冰块操作，冰块更新
		show();  // 显示新画面
	}
	gameover();     // 游戏结束、后续处理
}

void main()		//游戏主页功能
{
	initgraph(Width, High);	//定义画布
	IMAGE img_home;
	
	loadimage(&img_home, "E://菜单.jpg", Width * 2 / 3, High);
LOOP:
	putimage(Width / 6, 0, &img_home);	//主页菜单

	while (1)
	{
		if (MouseHit())//检测是否有鼠标消息
		{
			MOUSEMSG m;	//定义鼠标消息
			m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN)//单击左键
			{
				setlinecolor(LIGHTBLUE);
				circle(m.x, m.y, 5);	//鼠标点击显示

				if ((m.x < Width / 2 + 50 && m.x>Width / 2 - 50) && (m.y > High * 5 / 8 && m.y < High * 6 / 8))//点击位置
				{
					game();//进入游戏
				}
				else if ((m.x > Width / 6 && m.x<Width / 3) && (m.y >High * 2 / 3 && m.y < High * 2 / 3 + 70))
				{
					backstory();//游戏背景故事
				}

				else if ((m.x > Width * 5 / 6 - 70 && m.x<Width * 5 / 6) && (m.y >High - 70 && m.y < High))//点击位置
				{
					exit(0);//退出
				}
			}
		}
		if (back == 1)
		{
			back = 0;
			goto LOOP;//重返主页
		}
	}
	FlushMouseMsgBuffer();//清空鼠标信息缓存
	_getch();
	closegraph();
}